"""
项目中特殊的情况，例如：某些请求入参需要是 ObjectId，而不是字符串
"""

from bson import ObjectId
from bson.errors import InvalidId
from pydantic import GetCoreSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema

from my_typing import *
from utils.validator_utils.pydantic_x import PydanticCustomErrorBase


class ObjectIdError(PydanticCustomErrorBase):
    """ ObjectId 错误 """

    code = "invalid_object_id"
    msg_template = "invalid ObjectId <{object_id}>"
    msg_template_zh_cn = "无效的 ObjectId <{object_id}>"


class ObjectIdStr(str):
    """ ObjectId 字段（特殊的字符串） """

    def __new__(cls, v: AnyOrNone = None) -> "ObjectIdStr":
        if v is None:
            v = str(ObjectId())

        if not cls.is_valid(v):
            raise ValueError(ObjectIdError.msg_template_zh_cn.format(object_id=v))

        return super().__new__(cls, v)  # noqa

    @classmethod
    def _validate(cls, value: str) -> "ObjectIdStr":
        if isinstance(value, cls):
            return value
        return cls(value)

    @staticmethod
    def _serialize(value):
        try:
            return ObjectIdStr(ObjectId(value))

        except (InvalidId, TypeError, ValueError) as e:
            raise ObjectIdError(object_id=str(value)) from e

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        source: type[Any],
        handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        assert source is ObjectIdStr
        return core_schema.no_info_after_validator_function(
            cls._validate,
            core_schema.str_schema(),
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
                return_schema=core_schema.str_schema(),
            ),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls,
        _source_type: type[Any],
        _handler: GetCoreSchemaHandler
    ) -> JsonSchemaValue:
        return {
            "type": "string",
            "nullable": False,
            "title": "ObjectId",
            "description": "MongoDB 的 ObjectId 字符串",
            "minLength": 24,
            "maxLength": 24,
            "example": ObjectIdStr(),
        }

    @property
    def oid(self) -> ObjectId:
        """ 返回 bson.ObjectId 对象 """

        return ObjectId(self)

    @staticmethod
    def is_valid(oid: Any) -> bool:
        """ 是否是有效的 ObjectId """

        return ObjectId.is_valid(oid)


# Pydantic V1 版本的写法
# class ObjectIdStr(str):
#     """ ObjectId 字段（特殊的字符串） """
#
#     def __new__(cls, v: AnyOrNone = None) -> "ObjectIdStr":
#         if v is None:
#             v = str(ObjectId())
#
#         if not cls.is_valid(v):
#             raise ValueError(f"invalid ObjectId <{v}>")
#
#         return super().__new__(cls, v)  # noqa
#
#     @classmethod
#     def __get_validators__(cls):
#         yield cls.validate
#
#     @classmethod
#     def validate(cls, v) -> str:
#         try:
#             return ObjectIdStr(ObjectId(v))
#
#         except (InvalidId, TypeError, ValueError) as e:
#             # raise ObjectIdError(context=dict(object_id=str(v))) from e
#             raise ObjectIdError(object_id=str(v)) from e
#
#     @classmethod
#     def __modify_schema__(cls, field_schema: dict) -> None:  # Pydantic V1 的写法
#         """ Swagger 文档 字段说明 """
#
#         field_schema.update(
#             type="string",
#             format="ObjectId",
#             description="MongoDB 的 ObjectId 字符串",
#             example=str(ObjectId()),
#             maxLength=24,
#             minLength=24,
#         )
#
#     @property
#     def oid(self) -> ObjectId:
#         """ 返回 bson.ObjectId 对象 """
#
#         return ObjectId(self)
#
#     @staticmethod
#     def is_valid(oid: Any) -> bool:
#         """ 是否是有效的 ObjectId """
#
#         return ObjectId.is_valid(oid)


if __name__ == "__main__":
    oid_1: ObjectIdStr = ObjectIdStr()
    oid_list: list[str] = [
        ObjectIdStr()
        for _ in range(10)
    ]
    oid_list.append(oid_1)

    print(oid_1 in oid_list)
    print(str(oid_1) in oid_list)
